package cn.kimming.rpc.demo.ui.service.impl;

import cn.kimming.rpc.common.constant.LoadBalance;
import cn.kimming.rpc.common.constant.ProxyWay;
import cn.kimming.rpc.common.constant.SerializationWay;
import cn.kimming.rpc.demo.lb.service.LoadBalanceService;
import cn.kimming.rpc.demo.test.pojo.TestData;
import cn.kimming.rpc.demo.test.service.TestService;
import cn.kimming.rpc.demo.ui.dto.ServiceCallDTO;
import cn.kimming.rpc.demo.ui.service.UiService;
import cn.kimming.rpc.demo.ui.vo.AutoTestVO;
import cn.kimming.rpc.demo.ui.vo.LbServiceVO;
import cn.kimming.rpc.demo.ui.vo.ServiceVO;
import cn.kimming.rpc.demo.ui.vo.TreeDataVO;
import cn.kimming.rpc.remoting.client.RpcClientConfig;
import cn.kimming.rpc.remoting.proxy.RpcProxyFactory;
import cn.kimming.rpc.remoting.registry.ClientRpcRegistry;
import cn.kimming.rpc.remoting.registry.InstanceDetail;
import com.alibaba.fastjson.JSON;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.x.discovery.ServiceDiscovery;
import org.apache.curator.x.discovery.ServiceInstance;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.*;

@Service
public class UiServiceImpl implements UiService {

    @Value("${my.rpc.zk.url}")
    private String registryUrl;

    public String getRegistryUrl() {
        return registryUrl;
    }

    public void setRegistryUrl(String registryUrl) {
        this.registryUrl = registryUrl;
    }

    private LoadBalanceService randomService = RpcProxyFactory.createClient(LoadBalanceService.class, RpcClientConfig.configurer().loadBalance(LoadBalance.RANDOM).config());
    private LoadBalanceService roundRobinService = RpcProxyFactory.createClient(LoadBalanceService.class, RpcClientConfig.configurer().loadBalance(LoadBalance.ROUNDROBIN).config());
    private LoadBalanceService leastCallService = RpcProxyFactory.createClient(LoadBalanceService.class, RpcClientConfig.configurer().loadBalance(LoadBalance.LEASTCALL).config());

    private Map<String, TestService> testServiceMap;

    @PostConstruct
    public void init() {
        testServiceMap = new LinkedHashMap<>();
        ProxyWay[] proxyWays = ProxyWay.values();
        SerializationWay[] ways = SerializationWay.values();
        for (ProxyWay proxyWay : proxyWays) {
            if (proxyWay == ProxyWay.NONE) continue;
            for (SerializationWay way : ways) {
                if (way == SerializationWay.NONE) continue;
                TestService testService = RpcProxyFactory.createClient(TestService.class, RpcClientConfig.configurer().proxyWay(proxyWay).serializationWay(way).config());
                String combination = proxyWay.name().toLowerCase() + "+" + way.name().toLowerCase();
                testServiceMap.put(combination, testService);
            }
        }
    }

    @Override
    public List<ServiceVO> listAllServiceVO() {
        ServiceDiscovery<InstanceDetail> discovery = ClientRpcRegistry.getDiscovery(registryUrl);
        try {
            Collection<String> serviceNames = discovery.queryForNames();
            List<ServiceVO> serviceVoList = new ArrayList<>(serviceNames.size());
            for (String serviceName : serviceNames) {
                ServiceVO serviceVo = new ServiceVO();
                serviceVo.setServiceName(serviceName);
                Collection<ServiceInstance<InstanceDetail>> serviceInstances = discovery.queryForInstances(serviceName);
                for (ServiceInstance<InstanceDetail> serviceInstance : serviceInstances) {
                    ServiceVO.ServiceInstance instance = new ServiceVO.ServiceInstance();
                    instance.setId(serviceInstance.getId());
                    instance.setDescription(serviceInstance.getPayload().getDescription());
                    instance.setVersion(serviceInstance.getPayload().getVersion());
                    instance.setRegistrationTime(new Date(serviceInstance.getRegistrationTimeUTC()));
                    instance.setAddress(serviceInstance.getAddress());
                    instance.setPort(serviceInstance.getPort());
                    instance.setCallCount(serviceInstance.getPayload().getCallCount());
                    instance.setEnabled(serviceInstance.isEnabled());
                    serviceVo.addInstance(instance);
                }
                serviceVoList.add(serviceVo);
            }
            return serviceVoList;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    @Override
    public TreeDataVO getRegistryTreeData() {
        CuratorFramework client = ClientRpcRegistry.getClient(registryUrl);
        TreeDataVO root = new TreeDataVO("/");
        traversal(client, root, "/");
        return root;
    }

    private static void traversal(CuratorFramework client, TreeDataVO node, String nodePath) {
        try {
            List<String> childLabels = client.getChildren().forPath(nodePath);
            for (String childLabel : childLabels) {
                TreeDataVO child = new TreeDataVO(childLabel);
                node.addChild(child);
                String childPath = "/".equals(nodePath) ? "/" + childLabel : nodePath + "/" + childLabel;
                traversal(client, child, childPath);
            }
        }catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    @Override
    public Object getNodeInfo(String path) {
        CuratorFramework client = ClientRpcRegistry.getClient(registryUrl);
        try {
            byte[] bytes = client.getData().forPath(path);
            Object info = JSON.parse(bytes);
            return info;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    @Override
    public long testConsuming(ServiceCallDTO dto) {
        ProxyWay proxy = dto.getProxy();
        SerializationWay serialization = dto.getSerialization();
        int count = dto.getCount();
        RpcClientConfig config = RpcClientConfig.configurer().proxyWay(proxy).serializationWay(serialization).config();
        TestService client = RpcProxyFactory.createClient(TestService.class, config);
        TestData testData = new TestData();
        client.test(testData);
        long totalConsuming = 0L;
        for (int i = 0; i < count; i++) {
            long begin = System.currentTimeMillis();
            client.test(testData);
            totalConsuming += (System.currentTimeMillis() - begin);
        }
        return totalConsuming;
    }

    @Override
    public List<LbServiceVO> getLbServices() {
        List<LbServiceVO> list = new ArrayList<>();
        ServiceDiscovery<InstanceDetail> discovery = ClientRpcRegistry.getDiscovery(registryUrl);
        try {
            Collection<ServiceInstance<InstanceDetail>> serviceInstances = discovery.queryForInstances(LoadBalanceService.class.getCanonicalName());
            for (ServiceInstance<InstanceDetail> instance : serviceInstances) {
                String id = instance.getId();
                String address = instance.getAddress() + ":" + instance.getPort();
                int callCount = instance.getPayload().getCallCount();
                LbServiceVO lbServiceVO = new LbServiceVO(id, address, callCount);
                list.add(lbServiceVO);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return list;
    }

    @Override
    public String testLbService(LoadBalance loadBalance) {
        switch (loadBalance) {
            case RANDOM:
                return randomService.testLoadBalance();
            case ROUNDROBIN:
                return roundRobinService.testLoadBalance();
            case LEASTCALL:
                return leastCallService.testLoadBalance();
        }
        throw new RuntimeException();
    }

    @Override
    public List<AutoTestVO> autoTest() {
        List<AutoTestVO> list = new ArrayList<>();
        TestData testData = new TestData();
        testServiceMap.forEach((combination, testService) -> {
            testService.test(testData);
            long begin = System.currentTimeMillis();
            for (int i = 0; i < 100; i++) {
                testService.test(testData);
            }
            long consuming = System.currentTimeMillis() - begin;
            list.add(new AutoTestVO(combination, consuming));
        });
        return list;
    }
}
